diff --git a/HOCON.md b/HOCON.md index c563b0bf..7c74ca5e 100644 --- a/HOCON.md +++ b/HOCON.md @@ -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 diff --git a/README.md b/README.md index 2d6f3c72..0fea41c6 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/config/src/main/java/com/typesafe/config/ConfigFactory.java b/config/src/main/java/com/typesafe/config/ConfigFactory.java index 4773475d..64557440 100644 --- a/config/src/main/java/com/typesafe/config/ConfigFactory.java +++ b/config/src/main/java/com/typesafe/config/ConfigFactory.java @@ -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}). * + *

* 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 + *

+ * 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. setUseSystemProperties affects whether to fall back + * to system properties when they are not found in the config, but with + * load(), 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 Config + * object rather than loading "application.conf". The Config + * 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}. + * + *

+ * 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. setUseSystemProperties affects whether to fall back + * to system properties when they are not found in the config, but with + * load(), 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. + * + *

+ * Libraries and frameworks should ship with a "reference.conf" in their + * jar. + * + *

+ * The {@link #load()} methods merge this configuration for you + * automatically. + * + *

+ * Future versions may look for reference configuration in more places. It + * is not guaranteed that this method only 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. + * + *

+ * The {@link #load()} methods merge this configuration for you + * automatically. + * + *

+ * Future versions may get overrides in more places. It is not guaranteed + * that this method only 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 Config. 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 Config 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. + * + *

+ * {@link #load} will include the system properties as overrides already, as + * will {@link #defaultReference} and {@link #defaultOverrides}. + * + *

+ * 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 Config + */ public static Config systemProperties() { return ConfigImpl.systemPropertiesAsConfig(); } + /** + * Gets a Config containing the system's environment variables. + * This method can return a global immutable singleton. + * + *

+ * 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 Config + */ 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 HOCON + * spec. 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. * - * 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. + *

+ * If you want to have System.getProperties() 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 fileBasename * 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 Config. @@ -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()); } /** diff --git a/config/src/main/java/com/typesafe/config/ConfigValueFactory.java b/config/src/main/java/com/typesafe/config/ConfigValueFactory.java index 28e2f8bb..2f381f9a 100644 --- a/config/src/main/java/com/typesafe/config/ConfigValueFactory.java +++ b/config/src/main/java/com/typesafe/config/ConfigValueFactory.java @@ -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 @@ -117,7 +120,7 @@ public final class ConfigValueFactory { *

* See also {@link ConfigFactory#parseMap(Map)} which interprets the keys in * the map as path expressions. - * + * * @param values * @return a new {@link ConfigObject} */ diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java index 7c34b0d9..ddb81049 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -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; + } } diff --git a/config/src/main/java/com/typesafe/config/package.html b/config/src/main/java/com/typesafe/config/package.html index 0c735c5d..1e1c78bf 100644 --- a/config/src/main/java/com/typesafe/config/package.html +++ b/config/src/main/java/com/typesafe/config/package.html @@ -8,13 +8,35 @@ +

An API for loading and using configuration files, see the project site for more information. +

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.

+

+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. +

+ +

+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 Config parameter +and one which uses {@link com.typesafe.config.ConfigFactory#load()}. +

+ +

+You can find an example app and library on GitHub. +

+ diff --git a/config/src/test/scala/Rendering.scala b/config/src/test/scala/Rendering.scala index 310c3bab..857505b2 100644 --- a/config/src/test/scala/Rendering.scala +++ b/config/src/test/scala/Rendering.scala @@ -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()) diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index 93dc059b..08249ae1 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -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() diff --git a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala index 3d08b450..4a750c16 100644 --- a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala @@ -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")) diff --git a/examples/complex-app/src/main/resources/complex1.conf b/examples/complex-app/src/main/resources/complex1.conf new file mode 100644 index 00000000..3dada786 --- /dev/null +++ b/examples/complex-app/src/main/resources/complex1.conf @@ -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" \ No newline at end of file diff --git a/examples/complex-app/src/main/resources/complex2.conf b/examples/complex-app/src/main/resources/complex2.conf new file mode 100644 index 00000000..e7e98d77 --- /dev/null +++ b/examples/complex-app/src/main/resources/complex2.conf @@ -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" + } + } +} diff --git a/examples/complex-app/src/main/scala/ComplexApp.scala b/examples/complex-app/src/main/scala/ComplexApp.scala new file mode 100644 index 00000000..b3bf01e2 --- /dev/null +++ b/examples/complex-app/src/main/scala/ComplexApp.scala @@ -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") + } +} diff --git a/examples/simple-app/src/main/resources/application.conf b/examples/simple-app/src/main/resources/application.conf new file mode 100644 index 00000000..9fa122a0 --- /dev/null +++ b/examples/simple-app/src/main/resources/application.conf @@ -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" diff --git a/examples/simple-app/src/main/scala/SimpleApp.scala b/examples/simple-app/src/main/scala/SimpleApp.scala new file mode 100644 index 00000000..0efda2eb --- /dev/null +++ b/examples/simple-app/src/main/scala/SimpleApp.scala @@ -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") +} diff --git a/examples/simple-lib/src/main/resources/reference.conf b/examples/simple-lib/src/main/resources/reference.conf new file mode 100644 index 00000000..7a1ab9e8 --- /dev/null +++ b/examples/simple-lib/src/main/resources/reference.conf @@ -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" +} diff --git a/examples/simple-lib/src/main/scala/simplelib/SimpleLib.scala b/examples/simple-lib/src/main/scala/simplelib/SimpleLib.scala new file mode 100644 index 00000000..4161f579 --- /dev/null +++ b/examples/simple-lib/src/main/scala/simplelib/SimpleLib.scala @@ -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)) + } +} diff --git a/project/Build.scala b/project/Build.scala index a4a8e2d6..19fb4e63 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -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) }