diff --git a/runtime/engine/pom.xml b/runtime/engine/pom.xml
index 27d7ad4009..fd5e696f7a 100644
--- a/runtime/engine/pom.xml
+++ b/runtime/engine/pom.xml
@@ -222,6 +222,7 @@
io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/**/*.class
io/aklivity/zilla/runtime/engine/test/internal/**/*.schema.patch.json
io/aklivity/zilla/runtime/engine/test/internal/binding/**/*.class
+ io/aklivity/zilla/runtime/engine/test/internal/expression/**/*.class
io/aklivity/zilla/runtime/engine/test/internal/guard/**/*.class
io/aklivity/zilla/runtime/engine/test/internal/vault/**/*.class
io/aklivity/zilla/runtime/engine/internal/concurrent/bench/**/*.class
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java
index 72f57bdd04..7e1e4b1577 100644
--- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java
@@ -66,7 +66,7 @@ public class EngineConfiguration extends Configuration
public static final LongPropertyDef ENGINE_CREDITOR_CHILD_CLEANUP_LINGER_MILLIS;
public static final BooleanPropertyDef ENGINE_VERBOSE;
public static final IntPropertyDef ENGINE_WORKERS;
- public static final BooleanPropertyDef ENGINE_CONFIG_SYNTAX_MUSTACHE;
+ public static final BooleanPropertyDef ENGINE_CONFIG_RESOLVE_EXPRESSIONS;
private static final ConfigurationDef ENGINE_CONFIG;
@@ -104,7 +104,7 @@ public class EngineConfiguration extends Configuration
ENGINE_CREDITOR_CHILD_CLEANUP_LINGER_MILLIS = config.property("child.cleanup.linger", SECONDS.toMillis(5L));
ENGINE_VERBOSE = config.property("verbose", false);
ENGINE_WORKERS = config.property("workers", Runtime.getRuntime().availableProcessors());
- ENGINE_CONFIG_SYNTAX_MUSTACHE = config.property("config.syntax.mustache", false);
+ ENGINE_CONFIG_RESOLVE_EXPRESSIONS = config.property("config.resolve.expressions", true);
ENGINE_CONFIG = config;
}
@@ -278,9 +278,9 @@ public int workers()
return ENGINE_WORKERS.getAsInt(this);
}
- public boolean configSyntaxMustache()
+ public boolean configResolveExpressions()
{
- return ENGINE_CONFIG_SYNTAX_MUSTACHE.getAsBoolean(this);
+ return ENGINE_CONFIG_RESOLVE_EXPRESSIONS.getAsBoolean(this);
}
public Function hostResolver()
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/expression/ExpressionResolver.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/expression/ExpressionResolver.java
new file mode 100644
index 0000000000..0394c73ab5
--- /dev/null
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/expression/ExpressionResolver.java
@@ -0,0 +1,62 @@
+package io.aklivity.zilla.runtime.engine.expression;
+
+import static java.util.Collections.unmodifiableMap;
+import static java.util.Objects.requireNonNull;
+import static java.util.ServiceLoader.load;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class ExpressionResolver
+{
+ private static final Pattern EXPRESSION_PATTERN =
+ Pattern.compile("\\$\\{\\{\\s*([^\\s\\}]*)\\.([^\\s\\}]*)\\s*\\}\\}");
+
+ private final Map resolverSpis;
+ private Matcher matcher;
+
+ public static ExpressionResolver instantiate()
+ {
+ return instantiate(load(ExpressionResolverSpi.class));
+ }
+
+ public String resolve(
+ String template)
+ {
+ return matcher.reset(template)
+ .replaceAll(r -> resolve(r.group(1), r.group(2)));
+ }
+
+ private String resolve(
+ String context,
+ String var)
+ {
+ ExpressionResolverSpi resolver = requireNonNull(resolverSpis.get(context), "Unrecognized resolver name: " + context);
+ String value = resolver.resolve(var);
+ return value != null ? value : "";
+ }
+
+ private static ExpressionResolver instantiate(
+ ServiceLoader resolvers)
+ {
+ Map resolverSpisByName = new HashMap<>();
+ resolvers.forEach(resolverSpi -> resolverSpisByName.put(resolverSpi.name(), resolverSpi));
+ return new ExpressionResolver(unmodifiableMap(resolverSpisByName));
+ }
+
+ private Iterable names()
+ {
+ return resolverSpis.keySet();
+ }
+
+ private ExpressionResolver(
+ Map resolverSpis)
+ {
+ this.resolverSpis = resolverSpis;
+ this.matcher = EXPRESSION_PATTERN.matcher("");
+ }
+
+}
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/expression/ExpressionResolverSpi.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/expression/ExpressionResolverSpi.java
new file mode 100644
index 0000000000..c4fc12f0ae
--- /dev/null
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/expression/ExpressionResolverSpi.java
@@ -0,0 +1,10 @@
+package io.aklivity.zilla.runtime.engine.expression;
+
+public interface ExpressionResolverSpi
+{
+ String name();
+
+ String resolve(
+ String var);
+
+}
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/expression/EnvironmentResolverSpi.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/expression/EnvironmentResolverSpi.java
new file mode 100644
index 0000000000..a5754877fc
--- /dev/null
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/expression/EnvironmentResolverSpi.java
@@ -0,0 +1,20 @@
+package io.aklivity.zilla.runtime.engine.internal.expression;
+
+import io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi;
+
+public class EnvironmentResolverSpi implements ExpressionResolverSpi
+{
+
+ @Override
+ public String name()
+ {
+ return "env";
+ }
+
+ @Override
+ public String resolve(
+ String var)
+ {
+ return System.getenv(var);
+ }
+}
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/ConfigureTask.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/ConfigureTask.java
index bf7dc409a9..22709c0ef9 100644
--- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/ConfigureTask.java
+++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/ConfigureTask.java
@@ -66,6 +66,7 @@
import io.aklivity.zilla.runtime.engine.config.NamespaceConfig;
import io.aklivity.zilla.runtime.engine.config.RouteConfig;
import io.aklivity.zilla.runtime.engine.config.VaultConfig;
+import io.aklivity.zilla.runtime.engine.expression.ExpressionResolver;
import io.aklivity.zilla.runtime.engine.ext.EngineExtContext;
import io.aklivity.zilla.runtime.engine.ext.EngineExtSpi;
import io.aklivity.zilla.runtime.engine.guard.Guard;
@@ -73,7 +74,6 @@
import io.aklivity.zilla.runtime.engine.internal.config.NamespaceAdapter;
import io.aklivity.zilla.runtime.engine.internal.registry.json.UniquePropertyKeysSchema;
import io.aklivity.zilla.runtime.engine.internal.stream.NamespacedId;
-import io.aklivity.zilla.runtime.engine.internal.util.Mustache;
public class ConfigureTask implements Callable
{
@@ -91,6 +91,7 @@ public class ConfigureTask implements Callable
private final EngineExtContext context;
private final EngineConfiguration config;
private final List extensions;
+ private final ExpressionResolver expressions;
public ConfigureTask(
URL configURL,
@@ -118,6 +119,7 @@ public ConfigureTask(
this.context = context;
this.config = config;
this.extensions = extensions;
+ this.expressions = ExpressionResolver.instantiate();
}
@Override
@@ -161,13 +163,13 @@ else if ("http".equals(configURL.getProtocol()) || "https".equals(configURL.getP
}
}
- if (config.configSyntaxMustache())
+ logger.accept(configText);
+
+ if (config.configResolveExpressions())
{
- configText = Mustache.resolve(configText, System::getenv);
+ configText = expressions.resolve(configText);
}
- logger.accept(configText);
-
List errors = new LinkedList<>();
parse:
diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/util/Mustache.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/util/Mustache.java
deleted file mode 100644
index d9e46baf47..0000000000
--- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/util/Mustache.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2021-2022 Aklivity Inc.
- *
- * Aklivity licenses this file to you under the Apache License,
- * version 2.0 (the "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-package io.aklivity.zilla.runtime.engine.internal.util;
-
-import java.util.Optional;
-import java.util.function.UnaryOperator;
-import java.util.regex.Pattern;
-
-public final class Mustache
-{
- private static final Pattern MUSTACHE_PATTERN = Pattern.compile("\\{\\{\\s*env\\.([^\\s\\}]*)\\s*\\}\\}");
-
- public static String resolve(
- String template,
- UnaryOperator env)
- {
- return MUSTACHE_PATTERN
- .matcher(template)
- .replaceAll(r -> Optional.ofNullable(env.apply(r.group(1))).orElse(""));
- }
-
- private Mustache()
- {
- }
-}
diff --git a/runtime/engine/src/main/moditect/module-info.java b/runtime/engine/src/main/moditect/module-info.java
index a3866de715..2dc2dfc1ff 100644
--- a/runtime/engine/src/main/moditect/module-info.java
+++ b/runtime/engine/src/main/moditect/module-info.java
@@ -46,4 +46,8 @@
uses io.aklivity.zilla.runtime.engine.guard.GuardFactorySpi;
uses io.aklivity.zilla.runtime.engine.vault.VaultFactorySpi;
uses io.aklivity.zilla.runtime.engine.ext.EngineExtSpi;
+ uses io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi;
+
+ provides io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi
+ with io.aklivity.zilla.runtime.engine.internal.expression.EnvironmentResolverSpi;
}
diff --git a/runtime/engine/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi b/runtime/engine/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi
new file mode 100644
index 0000000000..a951fd79c1
--- /dev/null
+++ b/runtime/engine/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi
@@ -0,0 +1 @@
+io.aklivity.zilla.runtime.engine.internal.expression.EnvironmentResolverSpi
\ No newline at end of file
diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/expression/ExpressionResolverTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/expression/ExpressionResolverTest.java
new file mode 100644
index 0000000000..5796b6fd2c
--- /dev/null
+++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/expression/ExpressionResolverTest.java
@@ -0,0 +1,17 @@
+package io.aklivity.zilla.runtime.engine.expression;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class ExpressionResolverTest
+{
+ @Test
+ public void shouldLoadAndResolve()
+ {
+ ExpressionResolver expressions = ExpressionResolver.instantiate();
+ String actual = expressions.resolve("${{test.PASSWORD}}");
+
+ assertEquals("ACTUALPASSWORD", actual);
+ }
+}
diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/util/MustacheTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/util/MustacheTest.java
deleted file mode 100644
index e035fec764..0000000000
--- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/util/MustacheTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2021-2022 Aklivity Inc.
- *
- * Aklivity licenses this file to you under the Apache License,
- * version 2.0 (the "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-package io.aklivity.zilla.runtime.engine.internal.util;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.function.UnaryOperator;
-
-import org.junit.Test;
-
-public class MustacheTest
-{
- @Test
- public void shouldResolveIdentity()
- {
- String actual = Mustache.resolve("text without mustache", env -> null);
-
- assertEquals("text without mustache", actual);
- }
-
- @Test
- public void shouldResolveEnvironment()
- {
- UnaryOperator env = e -> "HOME".equals(e) ? "/home/user" : null;
- String actual = Mustache.resolve("HOME = {{ env.HOME }}", env);
-
- assertEquals("HOME = /home/user", actual);
- }
-}
diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/expression/TestExpressionResolverSpi.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/expression/TestExpressionResolverSpi.java
new file mode 100644
index 0000000000..82515975d6
--- /dev/null
+++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/expression/TestExpressionResolverSpi.java
@@ -0,0 +1,19 @@
+package io.aklivity.zilla.runtime.engine.test.internal.expression;
+
+import io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi;
+
+public class TestExpressionResolverSpi implements ExpressionResolverSpi
+{
+ @Override
+ public String name()
+ {
+ return "test";
+ }
+
+ @Override
+ public String resolve(
+ String var)
+ {
+ return "PASSWORD".equals(var) ? "ACTUALPASSWORD" : null;
+ }
+}
diff --git a/runtime/engine/src/test/resources/META-INF/services/io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi b/runtime/engine/src/test/resources/META-INF/services/io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi
new file mode 100644
index 0000000000..2ce7418079
--- /dev/null
+++ b/runtime/engine/src/test/resources/META-INF/services/io.aklivity.zilla.runtime.engine.expression.ExpressionResolverSpi
@@ -0,0 +1 @@
+io.aklivity.zilla.runtime.engine.test.internal.expression.TestExpressionResolverSpi
\ No newline at end of file