Last active
August 31, 2016 04:17
-
-
Save ericacm/4703252 to your computer and use it in GitHub Desktop.
Example of a Scala wrapper for Typesafe Config
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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