Skip to content

Instantly share code, notes, and snippets.

@vastdevblog
Created June 18, 2012 00:57
Show Gist options
  • Save vastdevblog/2946244 to your computer and use it in GitHub Desktop.
Save vastdevblog/2946244 to your computer and use it in GitHub Desktop.
Create @nAmed bindings for properties in a typesafe Config.
/**
* Copyright (c) 2012, Vast
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of the FreeBSD Project.
*/
package com.vast.common.config
import java.lang.{Boolean => JBoolean}
import scala.collection.JavaConversions._
import com.typesafe.config._
import com.google.inject.name.Names
import com.google.inject.{Key, Binder}
/**
* Bind the properties from a config. Each binding key is the
* name of the property prefixed by "config.".
*
* For example if you have
*
* a-root {
* a: 10
* b: "Goodbye"
* c: true
* nested: {
* a: "X"
* }
* aList: [
* "h"
* {
* b: "Y"
* }
* ]
* }
*
* b-root {
* x: "Hello World"
* }
*
* and you call bind(config, "a-root") you'll get properties including
* "config.a-root.a", "config.a-root.nested.a", and "config.a-root.aList.1.b".
* Not only the individual properties are bound but the intermediate configs,
* for example "config.a-root.nested".
*
* To ensure the correct order of creation you must create your config and
* ConfigBinder inside one of the configure methods in your modules. For example
*
* def configure() {
* var config = ConfigFactory.parseResources("com/vast/common/config/configBinder-test.conf")
* new ConfigBinder(binder()).bind(config, "a-root")
* bind(classOf[Config]).toInstance(config)
* }
*
* In addition the module that creates the config must be the last one
* listed in the Guice.createInjector call. For example, in the call below
* it is ConfigModuleOne that creates the Config instance and calls ConfigBinder.bind
*
* Guice.createInjector(Stage.PRODUCTION, new ConfigModuleTwo, new ConfigModuleOne)
*
* @author Alex Moffat (alex.moffat@vast.com)
*/
class ConfigBinder(binder: Binder) {
def bind(config: Config, root: String) {
bind(config.getObject(root), "config." + root)
}
private def bind(obj: ConfigValue, bindingPath: String) {
obj.valueType() match {
case ConfigValueType.OBJECT => {
val configObj = obj.asInstanceOf[ConfigObject]
// Bind the config from the object.
binder
.bind(Key.get(classOf[Config], Names.named(bindingPath)))
.toInstance(configObj.toConfig)
// Bind any nested values.
configObj.entrySet().foreach(me => {
val key = me.getKey
bind(me.getValue,
bindingPath + "." + key)
})
}
case ConfigValueType.LIST => {
val values = obj.asInstanceOf[ConfigList]
for (i <- 0 until values.size()) {
bind(values(i),
bindingPath + "." + i.toString)
}
}
case ConfigValueType.NUMBER => {
// Bind as string and rely on guice's conversion code when the value is used.
binder
.bindConstant()
.annotatedWith(Names.named(bindingPath))
.to(obj.unwrapped()
.asInstanceOf[Number].toString)
}
case ConfigValueType.BOOLEAN => {
binder
.bindConstant()
.annotatedWith(Names.named(bindingPath))
.to(obj.unwrapped()
.asInstanceOf[JBoolean])
}
case ConfigValueType.NULL => {
// NULL values are ignored.
}
case ConfigValueType.STRING => {
binder
.bindConstant()
.annotatedWith(Names.named(bindingPath))
.to(obj.unwrapped()
.asInstanceOf[String])
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment