mirror of
https://github.com/lightbend/config.git
synced 2025-01-28 21:20:07 +08:00
Add fallback ConfigReferenceResolver
This commit is contained in:
parent
76bb82cf58
commit
e1b327f837
@ -29,10 +29,13 @@ package com.typesafe.config;
|
||||
public final class ConfigResolveOptions {
|
||||
private final boolean useSystemEnvironment;
|
||||
private final boolean allowUnresolved;
|
||||
private final ConfigResolver resolver;
|
||||
|
||||
private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved) {
|
||||
private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved,
|
||||
ConfigResolver resolver) {
|
||||
this.useSystemEnvironment = useSystemEnvironment;
|
||||
this.allowUnresolved = allowUnresolved;
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,7 +45,7 @@ public final class ConfigResolveOptions {
|
||||
* @return the default resolve options
|
||||
*/
|
||||
public static ConfigResolveOptions defaults() {
|
||||
return new ConfigResolveOptions(true, false);
|
||||
return new ConfigResolveOptions(true, false, NULL_RESOLVER);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,7 +67,7 @@ public final class ConfigResolveOptions {
|
||||
* @return options with requested setting for use of environment variables
|
||||
*/
|
||||
public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
|
||||
return new ConfigResolveOptions(value, allowUnresolved);
|
||||
return new ConfigResolveOptions(value, allowUnresolved, resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,7 +94,55 @@ public final class ConfigResolveOptions {
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public ConfigResolveOptions setAllowUnresolved(boolean value) {
|
||||
return new ConfigResolveOptions(useSystemEnvironment, value);
|
||||
return new ConfigResolveOptions(useSystemEnvironment, value, resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options where the given resolver used as a fallback if a
|
||||
* reference cannot be otherwise resolved. This resolver will only be called
|
||||
* after resolution has failed to substitute with a value from within the
|
||||
* config itself and with any other resolvers that have been appended before
|
||||
* this one. Multiple resolvers can be added using,
|
||||
*
|
||||
* <pre>
|
||||
* ConfigResolveOptions options = ConfigResolveOptions.defaults()
|
||||
* .appendResolver(primary)
|
||||
* .appendResolver(secondary)
|
||||
* .appendResolver(tertiary);
|
||||
* </pre>
|
||||
*
|
||||
* With this config unresolved references will first be resolved with the
|
||||
* primary resolver, if that fails then the secondary, and finally if that
|
||||
* also fails the tertiary.
|
||||
*
|
||||
* If all fallbacks fail to return a substitution "allow unresolved"
|
||||
* determines whether resolution fails or continues.
|
||||
*`
|
||||
* @param value the resolver to fall back to
|
||||
* @return options that use the given resolver as a fallback
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public ConfigResolveOptions appendResolver(ConfigResolver value) {
|
||||
if (value == null) {
|
||||
throw new ConfigException.BugOrBroken("null resolver passed to appendResolver");
|
||||
} else if (value == this.resolver) {
|
||||
return this;
|
||||
} else {
|
||||
return new ConfigResolveOptions(useSystemEnvironment, allowUnresolved,
|
||||
this.resolver.withFallback(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resolver to use as a fallback if a substitution cannot be
|
||||
* otherwise resolved. Never returns null. This method is mostly used by the
|
||||
* config lib internally, not by applications.
|
||||
*
|
||||
* @return the non-null fallback resolver
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public ConfigResolver getResolver() {
|
||||
return this.resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,4 +155,22 @@ public final class ConfigResolveOptions {
|
||||
public boolean getAllowUnresolved() {
|
||||
return allowUnresolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton resolver that never resolves paths.
|
||||
*/
|
||||
private static final ConfigResolver NULL_RESOLVER = new ConfigResolver() {
|
||||
|
||||
@Override
|
||||
public ConfigValue lookup(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigResolver withFallback(ConfigResolver fallback) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
38
config/src/main/java/com/typesafe/config/ConfigResolver.java
Normal file
38
config/src/main/java/com/typesafe/config/ConfigResolver.java
Normal file
@ -0,0 +1,38 @@
|
||||
package com.typesafe.config;
|
||||
|
||||
/**
|
||||
* Implement this interface and provide an instance to
|
||||
* {@link ConfigResolveOptions#appendResolver ConfigResolveOptions.appendResolver()}
|
||||
* to provide custom behavior when unresolved substitutions are encountered
|
||||
* during resolution.
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public interface ConfigResolver {
|
||||
|
||||
/**
|
||||
* Returns the value to substitute for the given unresolved path. To get the
|
||||
* components of the path use {@link ConfigUtil#splitPath(String)}. If a
|
||||
* non-null value is returned that value will be substituted, otherwise
|
||||
* resolution will continue to consider the substitution as still
|
||||
* unresolved.
|
||||
*
|
||||
* @param path the unresolved path
|
||||
* @return the value to use as a substitution or null
|
||||
*/
|
||||
public ConfigValue lookup(String path);
|
||||
|
||||
/**
|
||||
* Returns a new resolver that falls back to the given resolver if this
|
||||
* one doesn't provide a substitution itself.
|
||||
*
|
||||
* It's important to handle the case where you already have the fallback
|
||||
* with a "return this", i.e. this method should not create a new object if
|
||||
* the fallback is the same one you already have. The same fallback may be
|
||||
* added repeatedly.
|
||||
*
|
||||
* @param fallback the previous includer for chaining
|
||||
* @return a new resolver
|
||||
*/
|
||||
public ConfigResolver withFallback(ConfigResolver fallback);
|
||||
|
||||
}
|
@ -6,6 +6,8 @@ import java.util.Collections;
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigRenderOptions;
|
||||
import com.typesafe.config.ConfigResolveOptions;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
import com.typesafe.config.ConfigValueType;
|
||||
|
||||
/**
|
||||
@ -88,7 +90,8 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable {
|
||||
v = result.value;
|
||||
newContext = result.context;
|
||||
} else {
|
||||
v = null;
|
||||
ConfigValue fallback = context.options().getResolver().lookup(expr.path().render());
|
||||
v = (AbstractConfigValue) fallback;
|
||||
}
|
||||
} catch (NotPossibleToResolve e) {
|
||||
if (ConfigImpl.traceSubstitutionsEnabled())
|
||||
|
@ -1233,4 +1233,70 @@ class ConfigTest extends TestUtils {
|
||||
val resolved = unresolved.resolveWith(source)
|
||||
assertEquals(43, resolved.getInt("foo"))
|
||||
}
|
||||
|
||||
/**
|
||||
* A resolver that replaces paths that start with a particular prefix with
|
||||
* strings where that prefix has been replaced with another prefix.
|
||||
*/
|
||||
class DummyResolver(prefix: String, newPrefix: String, fallback: ConfigResolver) extends ConfigResolver {
|
||||
|
||||
override def lookup(path: String): ConfigValue = {
|
||||
if (path.startsWith(prefix))
|
||||
ConfigValueFactory.fromAnyRef(newPrefix + path.substring(prefix.length))
|
||||
else if (fallback != null)
|
||||
fallback.lookup(path)
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
override def withFallback(f: ConfigResolver): ConfigResolver = {
|
||||
if (fallback == null)
|
||||
new DummyResolver(prefix, newPrefix, f)
|
||||
else
|
||||
new DummyResolver(prefix, newPrefix, fallback.withFallback(f))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def runFallbackTest(expected: String, source: String,
|
||||
allowUnresolved: Boolean, resolvers: ConfigResolver*) = {
|
||||
val unresolved = ConfigFactory.parseString(source)
|
||||
var options = ConfigResolveOptions.defaults().setAllowUnresolved(allowUnresolved)
|
||||
for (resolver <- resolvers)
|
||||
options = options.appendResolver(resolver)
|
||||
val obj = unresolved.resolve(options).root()
|
||||
assertEquals(expected, obj.render(ConfigRenderOptions.concise().setJson(false)))
|
||||
}
|
||||
|
||||
@Test
|
||||
def resolveFallback(): Unit = {
|
||||
runFallbackTest(
|
||||
"x=a,y=b",
|
||||
"x=${a},y=${b}", false,
|
||||
new DummyResolver("", "", null))
|
||||
runFallbackTest(
|
||||
"x=\"a.b.c\",y=\"a.b.d\"",
|
||||
"x=${a.b.c},y=${a.b.d}", false,
|
||||
new DummyResolver("", "", null))
|
||||
runFallbackTest(
|
||||
"x=${a.b.c},y=${a.b.d}",
|
||||
"x=${a.b.c},y=${a.b.d}", true,
|
||||
new DummyResolver("x.", "", null))
|
||||
runFallbackTest(
|
||||
"x=${a.b.c},y=\"e.f\"",
|
||||
"x=${a.b.c},y=${d.e.f}", true,
|
||||
new DummyResolver("d.", "", null))
|
||||
runFallbackTest(
|
||||
"w=\"Y.c.d\",x=${a},y=\"X.b\",z=\"Y.c\"",
|
||||
"x=${a},y=${a.b},z=${a.b.c},w=${a.b.c.d}", true,
|
||||
new DummyResolver("a.b.", "Y.", null),
|
||||
new DummyResolver("a.", "X.", null))
|
||||
|
||||
runFallbackTest("x=${a.b.c}", "x=${a.b.c}", true, new DummyResolver("x.", "", null))
|
||||
val e = intercept[ConfigException.UnresolvedSubstitution] {
|
||||
runFallbackTest("x=${a.b.c}", "x=${a.b.c}", false, new DummyResolver("x.", "", null))
|
||||
}
|
||||
assertTrue(e.getMessage.contains("${a.b.c}"))
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user