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