Skip to content

Instantly share code, notes, and snippets.

@ericacm
Last active August 31, 2016 04:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericacm/4703252 to your computer and use it in GitHub Desktop.
Save ericacm/4703252 to your computer and use it in GitHub Desktop.
Example of a Scala wrapper for Typesafe Config
import io.Source
import java.io.{FileInputStream, InputStream}
import com.typesafe.config.{ConfigException, Config, ConfigFactory}
import com.foo.dbfs.{FileSystem, Factory}
import com.foo.util.Logging
import com.foo.util.Environment
/**
* CslConfig manages CSL Configuration.
*
* CslConfig wraps the Typesafe Config class and adds support for Resource properties.
*
* Resource properties have URL-like values like "dbfs://foo.xml". Valid schemes are "classpath",
* "file" and "dbfs". When getResource() is called for a Resource property it returns a Resource object.
* Resource methods are asStream() and asString().
*
* There is only one CSL Configuration per application (it is global).
*
* The default location of the CSL Configuration is classpath://cslConfig.conf. This location can
* be overridden using the "csl.configPath" system property. The configPath points to a Resource object
* and thus can live in the classpath, DBFS or the filesystem.
*
* CslConfig also supports per-environment overrides. If the configPath is xxxx://yyyyy.conf and the
* current environment is DEV, then xxxx://yyyyy-DEV.conf will also be checked. Values in the -DEV file
* will be used for keys that exist in both files.
*/
object CslConfig extends Logging {
val cslConfigPathPropName = "csl.config.path"
val defaultConfigPath = "classpath://cslConfig.conf"
def config: CslConfig = {
synchronized {
configOpt match {
case Some(cfg) => cfg
case None =>
val propVal = System.getProperty(cslConfigPathPropName)
val configPath = Option(propVal).getOrElse(defaultConfigPath)
log.info(cslConfigPathPropName + "=" + propVal + " configPath: " + configPath)
val cfg = new CslConfig(configPath)
configOpt = Some(cfg)
cfg
}
}
}
private var configOpt: Option[CslConfig] = None
// Needed for Java since the config() method in the class clashes with the method in the companion
def getConfig = config
// The Typesafe config library caches system properties.
// Unit tests that change system properties need to call reset() after the system properties are changed.
def reset() {
configOpt = None
ConfigFactory.invalidateCaches()
}
}
class CslConfig(configPath: String) extends Logging {
def readConfig(configPath: String): Config = {
val resource = Resource(configPath)
val resourceStream = resource.asStream
val configString = Source.fromInputStream(resourceStream).mkString
ConfigFactory.parseString(configString)
}
private val config: Config = {
val cfg = readConfig(configPath)
val suffix = ".conf"
val envConfigPath = if (configPath.indexOf(suffix) > -1)
configPath.replace(suffix, "-" + Environment.currentEnvironment() + suffix)
else
configPath + "-" + Environment.currentEnvironment()
try {
val envConfig = readConfig(envConfigPath)
log.info("Found environment-specific config: " + envConfigPath)
val sp = ConfigFactory.systemProperties
sp.withFallback(envConfig).withFallback(cfg)
} catch {
case ex: Exception =>
log.info("Did not find environment-specific config: " + envConfigPath)
val sp = ConfigFactory.systemProperties
sp.withFallback(cfg)
}
}
def getString(path: String): String = config.getString(path)
def getInt(path: String): Int = config.getInt(path)
def getLong(path: String): Long = config.getLong(path)
def getBoolean(path: String): Boolean = optBoolean(path) getOrElse false
def getStringList(path: String): java.util.List[String] = config.getStringList(path)
def getResource(path: String): Resource = Resource(getString(path))
def getResource(path: String, defaultResourcePath: String): Resource =
Resource(optString(path).getOrElse(defaultResourcePath))
val catchMissing = util.control.Exception.catching(classOf[ConfigException.Missing])
def optString(path: String): Option[String] = catchMissing opt config.getString(path)
def optInt(path: String): Option[Int] = catchMissing opt config.getInt(path)
def optLong(path: String): Option[Long] = catchMissing opt config.getLong(path)
def optBoolean(path: String): Option[Boolean] = catchMissing opt config.getBoolean(path)
def optStringList(path: String): Option[java.util.List[String]] = catchMissing opt config.getStringList(path)
}
// TODO - use rapture.io?
class Resource(pathName: String) {
val validTypes = List("classpath://", "file://", "dbfs://")
if (pathName.contains(":") && !validTypes.exists(vt => pathName.startsWith(vt))) {
throw new Exception("Invalid resource type. Supported types=" + validTypes.mkString(", "))
}
val (pathType, path) = {
val uriPattern = """(\w+)://(.*)""".r
uriPattern findFirstIn pathName match {
case Some(uriPattern(pt, p)) => (pt, p)
case None => ("file", pathName)
}
}
def asStream: InputStream =
pathType match {
case "classpath" =>
val cl = Thread.currentThread().getContextClassLoader
cl.getResourceAsStream(path)
case "file" =>
new FileInputStream(path)
case x =>
sys.error("pathType '" + x + "' not implemented yet for asStream")
}
def asString: String = pathType match {
case "classpath" | "file" =>
Source.fromInputStream(asStream).mkString
case "dbfs" =>
readDbfsFile(pathName)
case x =>
sys.error("pathType '" + x + "' not implemented yet for asString")
}
def readDbfsFile(path: String): String = {
var fs: FileSystem = null
try {
fs = Factory.getInstance().getFileSystem
val str = fs.readFile(path)
str
}
finally {
if (fs != null) fs.cleanUp()
}
}
}
object Resource {
def apply(pathName: String) = new Resource(pathName)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment