Overhaul ConfigFactory API and ConfigFactory.load().

Update all docs and tests.

Add example library and application.
This commit is contained in:
Havoc Pennington 2011-11-28 12:23:21 -05:00
parent e6ee3d6232
commit 9aae9e2a80
17 changed files with 445 additions and 202 deletions

View File

@ -918,61 +918,40 @@ to access these strings that conflict with objects.
The usual rule in HOCON would be that the later assignment in the
file wins, rather than "object wins"; but implementing that for
Java properties would require implementing a custom Java
properties parser, which is surely not worth it.
properties parser, which is surely not worth it and wouldn't help
with system properties anyway.
### Root paths
### Conventional configuration files for JVM apps
By convention, a given application or library has a "root path."
Most commonly the root path has a single path element - "akka" for
example. But it could have multiple.
By convention, JVM apps have two parts to their configuration:
Conventional config file names and property names are derived from
the root path.
- the _reference_ config is made up of all resources named
`reference.conf` on the classpath, merged in the order they
are returned by `ClassLoader.getResources()`; also, system
property overrides are applied.
- the _application_ config can be loaded from anywhere an
application likes, but by default if the application doesn't
provide a config it would be loaded from files
`application.{conf,json,properties}` on the classpath and
then system property overrides are applied.
- a single JVM may have multiple application configs.
If an API looks like `load(rootPath)` then it would return an
object conceptually "at" the root path, not an object containing
the root path.
The reference config should be merged and resolved first, and
shared among all application configs. Substitutions in the
reference config are not affected by any application configs,
because the reference config should be resolved by itself.
### Conventional configuration file names for JVM apps
To get config file names, join the elements of the root path with
a hyphen, then add appropriate filename extensions.
If the root path is `foo.bar` (two elements, `foo` and `bar`),
then the configuration files should be searched for under the
following resource names on the classpath:
- /foo-bar.conf
- /foo-bar.json
- /foo-bar.properties
- /foo-bar-reference.conf
- /foo-bar-reference.json
- /foo-bar-reference.properties
The .json and .properties files are examples, different
implementations may support different file types. The "reference"
files are intended to contain defaults and be shipped with the
library or application being configured.
Note that the configuration files are absolute resource paths, not
relative to the package. So you would call
`klass.getResource("/foo-bar.conf")` not
`klass.getResource("foo-bar.conf")`.
The application config should then be loaded, have the reference
config added as a fallback, and have substitutions resolved. This
means the application config can refer to the reference config in
its substitutions.
### Conventional override by system properties
For an application's config, Java System properties _override_
HOCON found in the configuration file. This supports specifying
settings found in the configuration file. This supports specifying
config options on the command line.
Those system properties which begin with an application's root
path should override the configuration for that application.
For example, say your config is for root path "akka" then your
config key "foo" would go with `-Dakka.foo=10`. When loading your
config, any system properties starting with `akka.` would be
merged into the config.
### Substitution fallback to system properties
Recall that if a substitution is not present (not even set to
@ -980,30 +959,11 @@ Recall that if a substitution is not present (not even set to
for it from external sources. One such source could be Java system
properties.
To find a value for substitution, Java applications should look at
system properties directly, without the root path namespace.
Remember that namespaced system properties were already used as
overrides.
`${user.home}` would first look for a `user.home` in the
configuration tree (which has a scoped system property like
`akka.user.home` merged in!).
If no value for `${user.home}` exists in the configuration, the
implementation would look at system property `user.home` without
the `akka.` prefix.
The unprefixed system properties are _not_ merged in to the
configuration tree; if you iterate over your configuration, they
should not be in there. They are only used as a fallback when
evaluating substitutions.
The effect is to allow using generic system properties like
`user.home` and also to allow overriding those per-app.
So if someone wants to set their home directory for _all_ apps,
they set the `user.home` system property. If they then want to
force a particular home directory only for Akka, they could set
`akka.user.home` instead.
If you merge system properties in to an application's
configuration as overrides, then falling back to them for
substitutions won't add anything - they would already be found in
the configuration. But if you don't merge them in as overrides
then you could fall back to them.
### Substitution fallback to environment variables

View File

@ -49,24 +49,42 @@ and warrant that you have the legal authority to do so.
## API Example
ConfigRoot root = Config.load("myapp")
int bar1 = conf.getInt("foo.bar")
Config foo = conf.getConfig("foo")
int bar2 = obj.getInt("bar")
Config conf = ConfigFactory.load();
int bar1 = conf.getInt("foo.bar");
Config foo = conf.getConfig("foo");
int bar2 = obj.getInt("bar");
## Longer Examples
See the examples in the `examples/` directory.
You can run these from the sbt console with the commands `project
simple-app` and then `run`.
## Standard behavior
You can load any files and merge them in any order, but the
convenience method `Config.load()` loads the following
The convenience method `ConfigFactory.load()` loads the following
(first-listed are higher priority):
- `myapp.*` system properties
- `myapp.conf` (these files are all from classpath)
- `myapp.json`
- `myapp.properties`
- `myapp-reference.conf`
- `myapp-reference.json`
- `myapp-reference.properties`
- system properties
- `application.conf` (all resources on classpath with this name)
- `application.json` (all resources on classpath with this name)
- `application.properties` (all resources on classpath with this
name)
- `reference.conf` (all resources on classpath with this name)
The idea is that libraries and frameworks should ship with a
`reference.conf` in their jar. Applications should provide an
`application.conf`, or if they want to create multiple
configurations in a single JVM, they could use
`ConfigFactory.load("myapp")` to load their own `myapp.conf`.
Libraries and frameworks should default to `ConfigFactory.load()`
if the application does not provide a custom `Config`
object. Libraries and frameworks should also allow the application
to provide a custom `Config` object to be used instead of the
default, in case the application needs multiple configurations in
one JVM or wants to load extra config files from somewhere.
## JSON Superset
@ -238,17 +256,6 @@ Here are some features that might be nice to add.
(Note that regular `=` already merges object values, to avoid
object merge you have to first set the object to a non-object
such as null, then set a new object.)
- "application.conf": normally there is no "global"
configuration, each application does its own
`Config.load("myapp")`. However, it might be nice if you could
put all your config for your app and libraries you use in a
single file. This could be called "application.conf" for
example. `Config.load("myapp")` would load "application.conf"
and merge in the `"myapp"` object from "application.conf",
so if "application.conf" contained: `myapp { foo=3 }` then
the key `foo` would be set in the result of
`Config.load("myapp")`. Apps could then put all their config
in "application.conf", if desired.
- "delete": allow deleting a field, which is slightly different
from setting it to null (deletion allows fallback to values
in system properties and the environment, for example).

View File

@ -27,94 +27,233 @@ import com.typesafe.config.impl.Parseable;
* from a resource and nothing else.
*/
public final class ConfigFactory {
private ConfigFactory() {
}
/**
* Loads a configuration for the given root path in a "standard" way.
* Oversimplified, if your root path is foo.bar then this will load files
* from the classpath: foo-bar.conf, foo-bar.json, foo-bar.properties,
* foo-bar-reference.conf, foo-bar-reference.json,
* foo-bar-reference.properties. It will override all those files with any
* system properties that begin with "foo.bar.", as well.
*
* The root path should be a path expression, usually just a single short
* word, that scopes the package being configured; typically it's the
* package name or something similar. System properties overriding values in
* the configuration will have to be prefixed with the root path. The root
* path may have periods in it if you like but other punctuation or
* whitespace will probably cause you headaches. Example root paths: "akka",
* "sbt", "jsoup", "heroku", "mongo", etc.
* Loads an application's configuration from the given classpath resource or
* classpath resource basename, sandwiches it between default reference
* config and default overrides, and then resolves it. The classpath
* resource is "raw" (it should have no "/" prefix, and is not made relative
* to any package, so it's like {@link ClassLoader#getResource} not
* {@link Class#getResource}).
*
* <p>
* The loaded object will already be resolved (substitutions have already
* been processed). As a result, if you add more fallbacks then they won't
* be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If
* you want to parse additional files or something then you need to use
* loadWithoutResolving().
* {@link #load(Config)}.
*
* @param rootPath
* the configuration "domain"
* @return configuration for the requested root path
* @param resourceBasename
* name (optionally without extension) of a resource on classpath
* @return configuration for an application
*/
public static Config load(String rootPath) {
return loadWithoutResolving(rootPath).resolve();
}
public static Config load(String rootPath,
ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
return loadWithoutResolving(rootPath, parseOptions).resolve(
resolveOptions);
public static Config load(String resourceBasename) {
return load(resourceBasename, ConfigParseOptions.defaults(),
ConfigResolveOptions.defaults());
}
/**
* Like load() but does not resolve the config, so you can go ahead and add
* more fallbacks and stuff and have them seen by substitutions when you do
* call {@link Config#resolve()}.
* Like {@link #load(String)} but allows you to specify parse and resolve
* options.
*
* @param rootPath
* @return configuration for the requested root path
* <p>
* To be aware of: using
* {@link ConfigResolveOptions#setUseSystemProperties(boolean)
* setUseSystemProperties(false)} with this method has no meaningful effect,
* because the system properties are merged into the config as overrides
* anyway. <code>setUseSystemProperties</code> affects whether to fall back
* to system properties when they are not found in the config, but with
* <code>load()</code>, they will be in the config.
*
* @param resourceBasename
* the classpath resource name with optional extension
* @param parseOptions
* options to use when parsing the resource
* @param resolveOptions
* options to use when resolving the stack
* @return configuration for an application
*/
public static Config loadWithoutResolving(String rootPath) {
return loadWithoutResolving(rootPath, ConfigParseOptions.defaults());
public static Config load(String resourceBasename, ConfigParseOptions parseOptions,
ConfigResolveOptions resolveOptions) {
Config appConfig = ConfigFactory.parseResourcesAnySyntax(ConfigFactory.class, "/"
+ resourceBasename, parseOptions);
return load(appConfig, resolveOptions);
}
public static Config loadWithoutResolving(String rootPath,
ConfigParseOptions options) {
Config system = ConfigImpl.systemPropertiesWithPrefix(rootPath);
Config mainFiles = parseResourcesForPath(rootPath, options);
Config referenceFiles = parseResourcesForPath(rootPath + ".reference",
options);
return system.withFallback(mainFiles).withFallback(referenceFiles);
/**
* Assembles a standard configuration using a custom <code>Config</code>
* object rather than loading "application.conf". The <code>Config</code>
* object will be sandwiched between the default reference config and
* default overrides and then resolved.
*
* @param config
* the application's portion of the configuration
* @return resolved configuration with overrides and fallbacks added
*/
public static Config load(Config config) {
return load(config, ConfigResolveOptions.defaults());
}
/**
* Like {@link #load(Config)} but allows you to specify
* {@link ConfigResolveOptions}.
*
* <p>
* To be aware of: using
* {@link ConfigResolveOptions#setUseSystemProperties(boolean)
* setUseSystemProperties(false)} with this method has no meaningful effect,
* because the system properties are merged into the config as overrides
* anyway. <code>setUseSystemProperties</code> affects whether to fall back
* to system properties when they are not found in the config, but with
* <code>load()</code>, they will be in the config.
*
* @param config
* the application's portion of the configuration
* @param resolveOptions
* options for resolving the assembled config stack
* @return resolved configuration with overrides and fallbacks added
*/
public static Config load(Config config, ConfigResolveOptions resolveOptions) {
return defaultOverrides().withFallback(config).withFallback(defaultReference())
.resolve(resolveOptions);
}
/**
* Loads a default configuration, equivalent to {@link #load(String)
* load("application")}. This configuration should be used by libraries and
* frameworks unless an application provides a different one.
*
* @return configuration for an application
*/
public static Config load() {
return load("application");
}
/**
* Obtains the default reference configuration, which is currently created
* by merging all resources "reference.conf" found on the classpath and
* overriding the result with system properties. The returned reference
* configuration will already have substitutions resolved.
*
* <p>
* Libraries and frameworks should ship with a "reference.conf" in their
* jar.
*
* <p>
* The {@link #load()} methods merge this configuration for you
* automatically.
*
* <p>
* Future versions may look for reference configuration in more places. It
* is not guaranteed that this method <em>only</em> looks at
* "reference.conf".
*
* @return the default reference config
*/
public static Config defaultReference() {
return ConfigImpl.defaultReference();
}
/**
* Obtains the default override configuration, which currently consists of
* system properties. The returned override configuration will already have
* substitutions resolved.
*
* <p>
* The {@link #load()} methods merge this configuration for you
* automatically.
*
* <p>
* Future versions may get overrides in more places. It is not guaranteed
* that this method <em>only</em> uses system properties.
*
* @return the default override configuration
*/
public static Config defaultOverrides() {
return systemProperties();
}
/**
* Gets an empty configuration. See also {@link #empty(String)} to create an
* empty configuration with a description, which may improve user-visible
* error messages.
*
* @return an empty configuration
*/
public static Config empty() {
return empty(null);
}
/**
* Gets an empty configuration with a description to be used to create a
* {@link ConfigOrigin} for this <code>Config</code>. The description should
* be very short and say what the configuration is, like "default settings"
* or "foo settings" or something. (Presumably you will merge some actual
* settings into this empty config using {@link Config#withFallback}, making
* the description more useful.)
*
* @param originDescription
* description of the config
* @return an empty configuration
*/
public static Config empty(String originDescription) {
return ConfigImpl.emptyConfig(originDescription);
}
/**
* Gets a <code>Config</code> containing the system properties from
* {@link java.lang.System#getProperties()}, parsed and converted as with
* {@link #parseProperties}. This method can return a global immutable
* singleton, so it's preferred over parsing system properties yourself.
*
* <p>
* {@link #load} will include the system properties as overrides already, as
* will {@link #defaultReference} and {@link #defaultOverrides}.
*
* <p>
* Because this returns a singleton, it will not notice changes to system
* properties made after the first time this method is called.
*
* @return system properties parsed into a <code>Config</code>
*/
public static Config systemProperties() {
return ConfigImpl.systemPropertiesAsConfig();
}
/**
* Gets a <code>Config</code> containing the system's environment variables.
* This method can return a global immutable singleton.
*
* <p>
* Environment variables are used as fallbacks when resolving substitutions
* whether or not this object is included in the config being resolved, so
* you probably don't need to use this method for most purposes. It can be a
* nicer API for accessing environment variables than raw
* {@link java.lang.System#getenv(String)} though, since you can use methods
* such as {@link Config#getInt}.
*
* @return system environment variables parsed into a <code>Config</code>
*/
public static Config systemEnvironment() {
return ConfigImpl.envVariablesAsConfig();
}
/**
* Converts a Java Properties object to a ConfigObject using the rules
* documented in https://github.com/havocp/config/blob/master/HOCON.md The
* keys in the Properties object are split on the period character '.' and
* treated as paths. The values will all end up as string values. If you
* have both "a=foo" and "a.b=bar" in your properties file, so "a" is both
* the object containing "b" and the string "foo", then the string value is
* dropped.
* Converts a Java {@link java.util.Properties} object to a
* {@link ConfigObject} using the rules documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON
* spec</a>. The keys in the <code>Properties</code> object are split on the
* period character '.' and treated as paths. The values will all end up as
* string values. If you have both "a=foo" and "a.b=bar" in your properties
* file, so "a" is both the object containing "b" and the string "foo", then
* the string value is dropped.
*
* If you want to get System.getProperties() as a ConfigObject, it's better
* to use the systemProperties() or systemPropertiesRoot() methods. Those
* methods are able to use a cached global singleton ConfigObject for the
* system properties.
* <p>
* If you want to have <code>System.getProperties()</code> as a
* ConfigObject, it's better to use the {@link #systemProperties()} method
* which returns a cached global singleton.
*
* @param properties
* a Java Properties object
@ -126,18 +265,34 @@ public final class ConfigFactory {
return Parseable.newProperties(properties, options).parse().toConfig();
}
public static Config parseProperties(Properties properties) {
return parseProperties(properties, ConfigParseOptions.defaults());
}
public static Config parseReader(Reader reader, ConfigParseOptions options) {
return Parseable.newReader(reader, options).parse().toConfig();
}
public static Config parseReader(Reader reader) {
return parseReader(reader, ConfigParseOptions.defaults());
}
public static Config parseURL(URL url, ConfigParseOptions options) {
return Parseable.newURL(url, options).parse().toConfig();
}
public static Config parseURL(URL url) {
return parseURL(url, ConfigParseOptions.defaults());
}
public static Config parseFile(File file, ConfigParseOptions options) {
return Parseable.newFile(file, options).parse().toConfig();
}
public static Config parseFile(File file) {
return parseFile(file, ConfigParseOptions.defaults());
}
/**
* Parses a file with a flexible extension. If the <code>fileBasename</code>
* already ends in a known extension, this method parses it according to
@ -176,6 +331,10 @@ public final class ConfigFactory {
return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig();
}
public static Config parseFileAnySyntax(File fileBasename) {
return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults());
}
/**
* Parses all resources on the classpath with the given name and merges them
* into a single <code>Config</code>.
@ -211,6 +370,10 @@ public final class ConfigFactory {
.toConfig();
}
public static Config parseResources(Class<?> klass, String resource) {
return parseResources(klass, resource, ConfigParseOptions.defaults());
}
/**
* Parses classpath resources with a flexible extension. In general, this
* method has the same behavior as
@ -246,24 +409,16 @@ public final class ConfigFactory {
options).toConfig();
}
public static Config parseResourcesAnySyntax(Class<?> klass, String resourceBasename) {
return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults());
}
public static Config parseString(String s, ConfigParseOptions options) {
return Parseable.newString(s, options).parse().toConfig();
}
/**
* Parses classpath resources corresponding to this path expression.
* Essentially if the path is "foo.bar" then the resources are
* "/foo-bar.conf", "/foo-bar.json", and "/foo-bar.properties". If more than
* one of those exists, they are merged.
*
* @param rootPath
* @param options
* @return the parsed configuration
*/
public static Config parseResourcesForPath(String rootPath,
ConfigParseOptions options) {
// null originDescription is allowed in parseResourcesForPath
return ConfigImpl.parseResourcesForPath(rootPath, options).toConfig();
public static Config parseString(String s) {
return parseString(s, ConfigParseOptions.defaults());
}
/**

View File

@ -13,6 +13,9 @@ import com.typesafe.config.impl.ConfigImpl;
* data structures.
*/
public final class ConfigValueFactory {
private ConfigValueFactory() {
}
/**
* Creates a ConfigValue from a plain Java boxed value, which may be a
* Boolean, Number, String, Map, Iterable, or null. A Map must be a Map from

View File

@ -87,32 +87,6 @@ public class ConfigImpl {
return obj;
}
private static String makeResourceBasename(Path path) {
StringBuilder sb = new StringBuilder("/");
String next = path.first();
Path remaining = path.remainder();
while (next != null) {
sb.append(next);
sb.append('-');
if (remaining == null)
break;
next = remaining.first();
remaining = remaining.remainder();
}
sb.setLength(sb.length() - 1); // chop extra hyphen
return sb.toString();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourcesForPath(String expression,
final ConfigParseOptions baseOptions) {
Path path = Parser.parsePath(expression);
String basename = makeResourceBasename(path);
return parseResourcesAnySyntax(ConfigImpl.class, basename, baseOptions);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourcesAnySyntax(final Class<?> klass,
String resourceBasename, final ConfigParseOptions baseOptions) {
@ -281,16 +255,6 @@ public class ConfigImpl {
}
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
/* FIXME this is broken and will go away when Config.load() doesn't use it. */
public static Config systemPropertiesWithPrefix(String rootPath) {
try {
return systemPropertiesAsConfigObject().toConfig().getConfig(rootPath);
} catch (ConfigException.Missing e) {
return emptyObject("system properties").toConfig();
}
}
private static class SimpleIncluder implements ConfigIncluder {
private ConfigIncluder fallback;
@ -394,4 +358,17 @@ public class ConfigImpl {
public static Config envVariablesAsConfig() {
return envVariablesAsConfigObject().toConfig();
}
private static class ReferenceHolder {
private static final Config unresolvedResources = Parseable
.newResources(ConfigImpl.class, "/reference.conf", ConfigParseOptions.defaults())
.parse().toConfig();
static final Config referenceConfig = systemPropertiesAsConfig().withFallback(
unresolvedResources).resolve();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config defaultReference() {
return ReferenceHolder.referenceConfig;
}
}

View File

@ -8,13 +8,35 @@
</head>
<body bgcolor="white">
<p>
An API for loading and using configuration files, see <a href="https://github.com/havocp/config/">the project site</a>
for more information.
</p>
<p>
Typically you would load configuration with a static method from {@link com.typesafe.config.ConfigFactory} and then use
it with methods in the {@link com.typesafe.config.Config} interface.
</p>
<p>
An application can simply call {@link com.typesafe.config.ConfigFactory#load()} and place
its configuration in "application.conf" on the classpath.
If you use the default configuration from {@link com.typesafe.config.ConfigFactory#load()}
there's no need to pass a configuration to your libraries
and frameworks, as long as they all default to this same default, which they should.
</p>
<p>
A library or framework should ship a file "reference.conf" in its jar, and allow an application to pass in a
{@link com.typesafe.config.Config} to be used for the library. If no {@link com.typesafe.config.Config} is provided,
call {@link com.typesafe.config.ConfigFactory#load()}
to get the default one. Typically a library might offer two constructors, one with a <code>Config</code> parameter
and one which uses {@link com.typesafe.config.ConfigFactory#load()}.
</p>
<p>
You can find an example app and library <a href="https://github.com/havocp/config/tree/master/examples">on GitHub</a>.
</p>
</body>
</html>

View File

@ -2,7 +2,9 @@ import com.typesafe.config.ConfigFactory
object RenderExample extends App {
def render(what: String) {
val conf = ConfigFactory.loadWithoutResolving(what)
val conf = ConfigFactory.defaultOverrides()
.withFallback(ConfigFactory.parseResourcesAnySyntax(classOf[ConfigFactory], "/" + what))
.withFallback(ConfigFactory.defaultReference())
println("=== BEGIN UNRESOLVED " + what)
println(conf.root.render())

View File

@ -880,7 +880,7 @@ class ConfigTest extends TestUtils {
@Test
def renderRoundTrip() {
for (i <- 1 to 6) {
val conf = ConfigFactory.loadWithoutResolving("test0" + i)
val conf = ConfigFactory.parseResourcesAnySyntax(classOf[ConfigTest], "test0" + i)
val unresolvedRender = conf.root.render()
val resolved = conf.resolve()
val resolvedRender = resolved.root.render()

View File

@ -34,8 +34,8 @@ class PublicApiTest extends TestUtils {
@Test
def noSystemVariables() {
// should not have used system variables
val conf = ConfigFactory.load("test01", ConfigParseOptions.defaults(),
ConfigResolveOptions.noSystem())
val conf = ConfigFactory.parseResourcesAnySyntax(classOf[PublicApiTest], "/test01")
.resolve(ConfigResolveOptions.noSystem())
intercept[ConfigException.Missing] {
conf.getString("system.home")
@ -389,7 +389,7 @@ class PublicApiTest extends TestUtils {
@Test
def multipleResourcesUsed() {
val conf = ConfigFactory.parseResources(classOf[PublicApiTest], "/test01.conf", ConfigParseOptions.defaults())
val conf = ConfigFactory.parseResources(classOf[PublicApiTest], "/test01.conf")
assertEquals(42, conf.getInt("ints.fortyTwo"))
assertEquals(true, conf.getBoolean("test-lib.fromTestLib"))

View File

@ -0,0 +1,2 @@
simple-lib.foo="This value comes from complex-app's complex1.conf"
simple-lib.whatever = "This value comes from complex-app's complex1.conf"

View File

@ -0,0 +1,12 @@
complex-app {
# here we want a simple-lib-context unique to our app
# which can be custom-configured. In code, we have to
# pull out this subtree and pass it to simple-lib.
simple-lib-context = {
simple-lib {
foo="This value comes from complex-app's complex2.conf in its custom simple-lib-context"
whatever = "This value comes from complex-app's complex2.conf in its custom simple-lib-context"
}
}
}

View File

@ -0,0 +1,51 @@
import com.typesafe.config._
import simplelib._
object ComplexApp extends App {
// This app is "complex" because we load multiple separate app
// configs into a single JVM and we have a separately-configurable
// context for simple lib.
System.setProperty("simple-lib.whatever", "This value comes from a system property")
// "config1" is just an example of using a file other than application.conf
val config1 = ConfigFactory.load("complex1")
demoConfigInSimpleLib(config1)
// "config2" shows how to configure a library with a custom settings subtree
val config2 = ConfigFactory.load("complex2");
// pull out complex-app.simple-lib-context and move it to
// the toplevel, creating a new config suitable for our SimpleLibContext.
// The defaultOverrides() have to be put back on top of the stack so
// they still override any simple-lib settings.
// We fall back to config2 again to be sure we get simple-lib's
// reference.conf plus any other settings we've set. You could
// also just fall back to ConfigFactory.referenceConfig() if
// you don't want application.conf settings outside of
// complex-app.simple-lib-context to be used.
val simpleLibConfig2 = ConfigFactory.defaultOverrides()
.withFallback(config2.getConfig("complex-app.simple-lib-context"))
.withFallback(config2)
demoConfigInSimpleLib(simpleLibConfig2)
// Now let's illustrate that simple-lib will get upset if we pass it
// a bad config. In this case, we'll fail to merge the reference
// config in to complex-app.simple-lib-context, so simple-lib will
// point out that some settings are missing.
try {
demoConfigInSimpleLib(config2.getConfig("complex-app.simple-lib-context"))
} catch {
case e: ConfigException.ValidationFailed =>
println("when we passed a bad config to simple-lib, it said: " + e.getMessage)
}
def demoConfigInSimpleLib(config: Config) {
val context = new SimpleLibContext(config)
context.printSetting("simple-lib.foo")
context.printSetting("simple-lib.hello")
context.printSetting("simple-lib.whatever")
}
}

View File

@ -0,0 +1,2 @@
simple-lib.foo="This value comes from simple-app's application.conf"
simple-lib.whatever = "This value comes from simple-app's application.conf"

View File

@ -0,0 +1,14 @@
import com.typesafe.config._
import simplelib._
object SimpleApp extends App {
// example of how system properties override
System.setProperty("simple-lib.whatever", "This value comes from a system property")
// In this simple app, we're allowing SimpleLibContext() to
// use the default config in application.conf
val context = new SimpleLibContext()
context.printSetting("simple-lib.foo")
context.printSetting("simple-lib.hello")
context.printSetting("simple-lib.whatever")
}

View File

@ -0,0 +1,5 @@
simple-lib {
foo = "This value comes from simple-lib's reference.conf"
hello = "This value comes from simple-lib's reference.conf"
whatever = "This value comes from simple-lib's reference.conf"
}

View File

@ -0,0 +1,22 @@
package simplelib
import com.typesafe.config._
// we have a constructor allowing the app to provide a custom Config
class SimpleLibContext(config: Config) {
// This verifies that the Config is sane and has our
// reference config.
config.checkValid(ConfigFactory.defaultReference())
// This uses the standard default Config, if none is provided,
// which simplifies apps willing to use the defaults
def this() {
this(ConfigFactory.load())
}
// this is the amazing functionality provided by simple-lib
def printSetting(path: String) {
println("The setting '" + path + "' is: " + config.getString(path))
}
}

View File

@ -3,11 +3,20 @@ import Keys._
object ConfigBuild extends Build {
lazy val root = Project(id = "root",
base = file(".")) aggregate(testLib, configLib)
base = file(".")) aggregate(testLib, configLib, simpleLib, simpleApp)
lazy val configLib = Project(id = "config",
base = file("config")) dependsOn(testLib % "test->test")
lazy val testLib = Project(id = "test-lib",
base = file("test-lib"))
lazy val simpleLib = Project(id = "simple-lib",
base = file("examples/simple-lib")) dependsOn(configLib)
lazy val simpleApp = Project(id = "simple-app",
base = file("examples/simple-app")) dependsOn(simpleLib)
lazy val complexApp = Project(id = "complex-app",
base = file("examples/complex-app")) dependsOn(simpleLib)
}