diff --git a/pom.xml b/pom.xml index 0710a38..5d28d8a 100644 --- a/pom.xml +++ b/pom.xml @@ -134,6 +134,11 @@ byte-buddy 1.14.13 + + org.codehaus.groovy + groovy + 3.0.17 + diff --git a/src/main/java/ru/ewc/checklogic/FileStateFactory.java b/src/main/java/ru/ewc/checklogic/FileStateFactory.java index 2b62497..05d309d 100644 --- a/src/main/java/ru/ewc/checklogic/FileStateFactory.java +++ b/src/main/java/ru/ewc/checklogic/FileStateFactory.java @@ -29,12 +29,14 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.SneakyThrows; import org.yaml.snakeyaml.Yaml; +import ru.ewc.checklogic.testing.FunctionsLocator; import ru.ewc.decisions.api.InMemoryLocator; import ru.ewc.decisions.api.Locator; import ru.ewc.state.State; @@ -65,9 +67,19 @@ public State initialState() { this.locators.clear(); this.loadLocatorsFromApplicationConfig(); this.loadInMemoryRequestLocator(); + this.loadFunctionsLocator(); return new State(this.locators); } + private void loadFunctionsLocator() { + this.locators.add( + new FunctionsLocator( + this.config.functionsLocatorName(), + Paths.get(this.config.getRoot(), "functions") + ) + ); + } + private void loadInMemoryRequestLocator() { this.locators.add( new InMemoryLocator(this.config.requestLocatorName(), new HashMap<>()) diff --git a/src/main/java/ru/ewc/checklogic/ServerConfiguration.java b/src/main/java/ru/ewc/checklogic/ServerConfiguration.java index e4b352f..addb02b 100644 --- a/src/main/java/ru/ewc/checklogic/ServerConfiguration.java +++ b/src/main/java/ru/ewc/checklogic/ServerConfiguration.java @@ -48,7 +48,8 @@ public ServerConfiguration(final String root) { this.parameters = new HashMap<>( Map.of( "request", "request", - "command", "available" + "command", "available", + "function", "function" ) ); } @@ -88,4 +89,8 @@ public Path applicationConfig() { public String getRoot() { return this.root; } + + public String functionsLocatorName() { + return this.parameters.get("function"); + } } diff --git a/src/main/java/ru/ewc/checklogic/testing/FunctionsLocator.java b/src/main/java/ru/ewc/checklogic/testing/FunctionsLocator.java new file mode 100644 index 0000000..8261047 --- /dev/null +++ b/src/main/java/ru/ewc/checklogic/testing/FunctionsLocator.java @@ -0,0 +1,92 @@ +/* + * MIT License + * + * Copyright (c) 2024 Decision-Driven Development + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package ru.ewc.checklogic.testing; + +import groovy.lang.Binding; +import groovy.util.GroovyScriptEngine; +import java.net.URL; +import java.nio.file.Path; +import java.util.HashMap; +import lombok.SneakyThrows; +import ru.ewc.decisions.api.ComputationContext; +import ru.ewc.decisions.api.DecitaException; +import ru.ewc.decisions.api.InMemoryLocator; +import ru.ewc.decisions.api.Locator; + +/** + * I am a special locator that can load functions from Groovy scripts. My main responsibility is to + * run the Groovy script and return the result of the script execution. + * + * @since 0.4.1 + */ +public final class FunctionsLocator implements Locator { + /** + * The locator's name. + */ + private final String name; + + /** + * The in-memory locator used to store overridden values. + */ + private final InMemoryLocator locator; + + /** + * Groovy script engine used to run the Groovy scripts. + */ + private final GroovyScriptEngine engine; + + @SneakyThrows + public FunctionsLocator(final String name, final Path path) { + this.name = name; + this.locator = new InMemoryLocator("locator", new HashMap<>()); + this.engine = new GroovyScriptEngine(new URL[]{path.toUri().toURL()}); + } + + @SneakyThrows + @Override + public String fragmentBy( + final String fragment, + final ComputationContext context + ) throws DecitaException { + final String result; + if (this.locator.state().containsKey(fragment)) { + result = this.locator.fragmentBy(fragment, context); + } else { + final Binding binding = new Binding(); + binding.setVariable("context", context); + result = this.engine.run("%s.groovy".formatted(fragment), binding).toString(); + } + return result; + } + + @Override + public void setFragmentValue(final String fragment, final String value) { + this.locator.setFragmentValue(fragment, value); + } + + @Override + public String locatorName() { + return this.name; + } +}