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